package com.zeratul.common.i18n.source.nacos;

import com.zeratul.common.i18n.source.nacos.base.NacosResourceMessageSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.CharArrayReader;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Nacos配置中心基于Properties格式 管理I18n国际化配置文件的数据源
 * 这种管理方式可能导致会有多个国际化配置文件，一种语言对应一个properties配置文件，同时还分为通用国际化语言 和 特定微服务国际化语言
 *
 * 比如：
 * 通用汉语国际化配置 对应配置文件 - dataId：message_zh_CN.properties    groupName：base
 * 特定微服务英语国际化配置 对应配置文件 - dataId：message_en_US.properties    groupName：微服务名称
 */
@Slf4j
public class PropNacosResourceMessageSource extends NacosResourceMessageSource {


    /**
     * 本地缓存集合
     * key - Locale 语言环境
     * value - String 对应的配置文件名称列表
     */
    private Map<Locale, List<String>> localeCacheMap = new ConcurrentHashMap<>();


    /**
     * 根据语言环境 读取对应的配置文件名称
     * @param locale
     * @return
     */
    @Override
    protected List<String> getFileNames(Locale locale) {
        //从缓存中读取
        List<String> cacheFileNames = this.localeCacheMap.get(locale);
        //如果缓存中存在，则直接返回
        if(!CollectionUtils.isEmpty(cacheFileNames)) {
            return cacheFileNames;
        }

        //进行加锁操作，防止并发
        synchronized (locale) {
            //双重锁判定，保证一个语言环境一次只有一个线程会进行Nacos读取
            cacheFileNames = this.localeCacheMap.get(locale);
            //如果缓存中存在，则直接返回
            if(!CollectionUtils.isEmpty(cacheFileNames)) {
                return cacheFileNames;
            }

            //否则从nacos中读取配置文件
            cacheFileNames = createFilenamesForLocale(locale);
            if (CollectionUtils.isEmpty(cacheFileNames)) {
                return null;
            }
            //放入缓存中
            this.localeCacheMap.put(locale, cacheFileNames);
        }
        return cacheFileNames;
    }

    /**
     * 根据语言环境 组装文件名称
     * @return
     */
    protected List<String> createFilenamesForLocale(Locale locale){
        //获取所有设置的BeanName集合
        List<String> fileNames = Optional.ofNullable(getBasenameSet())
                .map(baseNames -> baseNames.stream().flatMap(s -> createFilenamesForBaseNameAndLocale(s, locale).stream()).collect(Collectors.toList()))
                .orElse(null);
        return fileNames;
    }

    /**
     * 根据baseName 和 语言环境 组装多种配置文件名称
     *
     * 比如：baseName=message locale=zh_CN
     * 可以组装的配置文件：
     *  message_zh_CN
     *  message_zh
     *  message
     *
     * @param basename 基准文件名称
     * @param locale 语言环境
     * @return
     */
    protected List<String> createFilenamesForBaseNameAndLocale(String basename, Locale locale) {
        if (!StringUtils.hasLength(basename) || Objects.isNull(locale)) {
            return null;
        }

        List<String> result = new ArrayList<>(3);
        String language = locale.getLanguage();
        String country = locale.getCountry();
        StringBuilder temp = new StringBuilder(basename);
        result.add(temp.toString());

        temp.append('_');
        if (StringUtils.hasLength(language)) {
            temp.append(language);
            result.add(0, temp.toString());
        }

        temp.append('_');
        if (StringUtils.hasLength(country)) {
            temp.append(country);
            result.add(0, temp.toString());
        }
        return result;
    }


    /**
     * 创建BaseHolder对象 - 由子类决定创建什么对象
     * @return
     */
    @Override
    protected BaseHolder createBaseHolder() {
        return new PropertiesHolder();
    }


    /**
     * 一个配置对象的包装类
     */
    @Data
    private static class PropertiesHolder extends BaseHolder<Properties>{

        /**
         * 转换方法，将配置信息，转换成相应的对象（具体对象由子类决定）
         *
         * @param config
         * @return
         */
        @Override
        public Properties convertConfig(String config) {
            Properties properties = new Properties();

            //如果没有配置数据，则返回一个空的Properties对象
            if (!StringUtils.hasLength(config)) {
                return properties;
            }

            //将配置加载到Properties配置文件中
            try (CharArrayReader inputStream = new CharArrayReader(config.toCharArray())) {
                properties.load(inputStream);
            } catch (IOException e) {
                log.error("【国际化-基于Nacos配置】将配置信息转换为Properties对象失败，配置内容为：{}", config, e);
                return null;
            }
            return properties;
        }

        /**
         * 从Holder对象中，根据语言 和 code 获取国际化value
         *
         * @param properties
         * @param key
         * @return
         */
        @Override
        public String getValue(Properties properties, Locale locale, String key) {
            return properties.getProperty(key);
        }

        /**
         * 返回配置后缀
         *
         * @return
         */
        @Override
        protected String getSuffix() {
            return "properties";
        }
    }
}
